/*******************************************************************************
 * Copyright (c) 2012-2017 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.core.db.schema.impl.flyway;

import com.google.inject.Inject;
import com.google.inject.name.Named;

import org.eclipse.che.core.db.schema.SchemaInitializationException;
import org.eclipse.che.core.db.schema.SchemaInitializer;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.internal.dbsupport.DbSupport;
import org.flywaydb.core.internal.dbsupport.DbSupportFactory;
import org.flywaydb.core.internal.util.PlaceholderReplacer;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * <a href="https://flywaydb.org/">Flyway</a> based schema initializer.
 *
 * @author Yevhenii Voevodin
 */
public class FlywaySchemaInitializer implements SchemaInitializer {

    private final DataSource          dataSource;
    private final String[]            locations;
    private final String              scriptsPrefix;
    private final String              scriptsSuffix;
    private final String              versionSeparator;
    private final boolean             baselineOnMigrate;
    private final String              baselineVersion;
    private final PlaceholderReplacer placeholderReplacer;

    /**
     * Creates a new instance of flyway schema initializer.
     *
     * @param scriptsLocations
     *         the locations where to search migration scripts,
     *         if locations is not prefixed or prefixed with <i>classpath:</i>
     *         then initializer will try to find scripts in classpath using
     *         {@code Thread.currentThread().}{@link Thread#getContextClassLoader() getContextClassLoader()}
     * @param scriptsPrefix
     *         prefix of migration scripts e.g. 'v' or empty string
     * @param scriptsSuffix
     *         suffix of migration scripts e.g. '.sql'
     * @param versionSeparator
     *         separate version from the other part of script name e.g. '.' or '__'
     * @param baselineOnMigrate
     *         whether to ignore scripts up to the version configured by {@code baselineVersion}
     * @param baselineVersion
     *         up to this version all the scripts ignored, unless schema is initialized first time,
     *         note that scripts with version equal to baseline version are also ignored
     * @param dataSource
     *         data source used for migrations
     * @param placeholderReplacer
     *         used to replace variables in script with configured values
     */
    @Inject
    public FlywaySchemaInitializer(@Named("db.schema.flyway.scripts.locations") String[] scriptsLocations,
                                   @Named("db.schema.flyway.scripts.prefix") String scriptsPrefix,
                                   @Named("db.schema.flyway.scripts.suffix") String scriptsSuffix,
                                   @Named("db.schema.flyway.scripts.version_separator") String versionSeparator,
                                   @Named("db.schema.flyway.baseline.enabled") boolean baselineOnMigrate,
                                   @Named("db.schema.flyway.baseline.version") String baselineVersion,
                                   DataSource dataSource,
                                   PlaceholderReplacer placeholderReplacer) {
        this.dataSource = dataSource;
        this.locations = scriptsLocations;
        this.scriptsPrefix = scriptsPrefix;
        this.scriptsSuffix = scriptsSuffix;
        this.versionSeparator = versionSeparator;
        this.baselineOnMigrate = baselineOnMigrate;
        this.baselineVersion = baselineVersion;
        this.placeholderReplacer = placeholderReplacer;
    }

    /** Creates a new flyway based initializer with default values. */
    public FlywaySchemaInitializer(DataSource dataSource, String... locations) {
        this(locations,
             "",
             ".sql",
             "__",
             false,
             "",
             dataSource,
             PlaceholderReplacer.NO_PLACEHOLDERS);
    }

    @Override
    public void init() throws SchemaInitializationException {
        try (final Connection conn = dataSource.getConnection()) {
            final Flyway flyway = new Flyway();
            flyway.setDataSource(dataSource);
            flyway.setLocations(locations);
            flyway.setClassLoader(Thread.currentThread().getContextClassLoader());
            final DbSupport dbSupport = DbSupportFactory.createDbSupport(conn, true);
            final String productName = conn.getMetaData().getDatabaseProductName().toLowerCase();
            flyway.setResolvers(new CustomSqlMigrationResolver(productName, dbSupport, placeholderReplacer));
            flyway.setSkipDefaultResolvers(true);
            flyway.setBaselineOnMigrate(baselineOnMigrate);
            if (baselineOnMigrate) {
                flyway.setBaselineVersionAsString(baselineVersion);
            }
            flyway.setSqlMigrationSeparator(versionSeparator);
            flyway.setSqlMigrationSuffix(scriptsSuffix);
            flyway.setSqlMigrationPrefix(scriptsPrefix);
            flyway.migrate();
        } catch (SQLException | RuntimeException x) {
            throw new SchemaInitializationException(x.getLocalizedMessage(), x);
        }
    }
}
